#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // 包含了文件操作相关 struct 的定义，例如ile_operations

#include <linux/kdev_t.h>  // 操作设备号
#include <linux/types.h> //设备号类型dev_t
#include <linux/cdev.h>	// 字符设备结构体
#include <linux/device.h> //包含了 device、class 等结构的定义

#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/delay.h>

// 这是通过insmod装载时传递的两个参数为主设备号和次设备号 例如：insmod chardev_driver major_num = 200, minor_num = 0
static int major_num, minor_num; //定义主设备号和次设备号
module_param(major_num, int, S_IRUSR); //驱动模块传入普通参数 major_num
module_param(minor_num, int, S_IRUSR); //驱动模块传入普通参数 minor_nu

/*
 * @description     : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 设备文件，file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return          : 0 成功;其他 失败
 */
static int chardev_open (struct inode *inode, struct file *file)
{
    printk(KERN_EMERG "hello chardev_open!\n");
    return 0;
}

/*
 * @description     : 关闭/释放设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 要关闭的设备文件(文件描述符)
 * @return          : 0 成功; 其他 失败
 */
static int chardev_release(struct inode *inode, struct file *file)
{
    printk(KERN_EMERG "bye bye chardev_release!\n");
    return 0;
}

/*
 * @description     : 从设备读取数据 
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数，如果为负值，表示读取失败
 */
static ssize_t chardev_read(struct file *file, char __user *ubuf, size_t cnt, loff_t *offt)
{
    int ret;
    char data[15] = "hello chardev!";
    ret = copy_to_user(ubuf, data, sizeof(data));

    printk(KERN_EMERG "hello chardev_read!\n");
    return ret;
}

/*
 * @description     : 从设备节点写入数据 
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要写入的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数，如果为负值，表示读取失败
 */
static ssize_t chardev_write(struct file *file, const char __user *ubuf, size_t cnt, loff_t *offt)
{
    char kbuf[64] = "copy from user!\n";
    if( copy_from_user(kbuf, ubuf, cnt) != 0 ){
        printk(KERN_EMERG "copy_from_user error!\n");
        return -1;
    }
    printk("buf is:%s\n", kbuf);

    printk(KERN_EMERG "hello chardev_write!\n");
    return 0;
}

/* 操作函数结构体 */
struct file_operations chardev_fops = {
    .owner      = THIS_MODULE,
    .open       = chardev_open,
    .release    = chardev_release,
    .read       = chardev_read,
    .write      = chardev_write,
};


static dev_t dev;// dev_t 是个 32 位的变量，其中 12 位用来表示主设备号，20 位用来表示次设备号 
static struct cdev cdev;// 字符设备结构体 
struct class *class;// 类 
struct device *device; // 设备 

/*
 * @description : 驱动入口函数
 * @param       : 无
 * @return      : 无
 */
#define CHARDEV_NAME    		"chardev_name"	//定义设备名称 在/proc/devices下查看
#define DEVICE_NUMBER 			1		 		//定义次设备号的个数
#define DEVICE_MINOR_NUMBER 	0 				//定义次设备号的起始地址
#define DEVICE_CLASS_NAME 		"chrdev_class" 	// 在/sys/class下查看
#define DEVICE_NODE_NAME 		"chartest" 		// 定义设备节点的名字 在/dev/下查看
static int chardev_init(void)
{
    int ret;
	if(major_num != 0) 
	{
        // 静态注册设备号
		printk("major_num = %d minor_num = %d\n", major_num, minor_num); //打印传入进来的设备号
		dev = MKDEV(major_num, minor_num); //MKDEV 将主设备号和次设备号合并为一个设备号
		ret = register_chrdev_region(dev, DEVICE_NUMBER, CHARDEV_NAME); //注册设备号
		if (ret < 0)
		{
			printk(KERN_ALERT "register_chrdev_region error\n");
			return -1;
		}
		//静态注册设备号成功,则打印。
		printk("register_chrdev_region ok\n");
		
	}else 
	{
        // 动态注册设备号
		ret = alloc_chrdev_region(&dev, 0, 1, CHARDEV_NAME);
		if (ret < 0)
		{
			printk(KERN_ALERT "Failed to allocate character device region\n");
			return -1;
		}
		
		major_num = MAJOR(dev);
		minor_num = MINOR(dev);
		
		printk("major_num = %d minor_num = %d\n",major_num, minor_num); //打印申请来的设备号
		//动态注册设备号成功
		printk("alloc_chrdev_region ok\n");
	}
    
	// 字符设备的注册
    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &chardev_fops); // 函数初始化 cdev 结构体成员变量
	ret = cdev_add(&cdev, dev, DEVICE_NUMBER); // 添加字符设备到内核
    if (ret < 0)
    {
        unregister_chrdev_region(dev, DEVICE_NUMBER);
        printk(KERN_ALERT "Failed to add character device\n");
        return -1;
    }
	
	// 自动创建设备节点
	class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
	// 在 class 类下创建设备
	device = device_create(class, NULL, dev, NULL, DEVICE_NODE_NAME);
	
	
    printk(KERN_INFO "Character device registered: %s DEVICE_NODE_NAME = %s\n", CHARDEV_NAME, DEVICE_NODE_NAME);

    return 0;
}

/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static void chardev_exit(void)
{
    // 注销字符设备
    cdev_del(&cdev);
	//注销设备
	device_destroy(class, dev);
	// 注销类
	class_destroy(class);
	// 注销设备号
    unregister_chrdev_region(dev, DEVICE_NUMBER);
    printk(KERN_INFO "Character device unregistered\n");
}

module_init(chardev_init);
module_exit(chardev_exit);

MODULE_LICENSE("GPL");              //声明模块拥有开源许可
MODULE_AUTHOR("xxxx");              //声明作者
